#include "image_cache.h"
#include <cstring>
#include <cstdio>
#include <cstdlib>


void ImageCache::abort(const std::string& msg,FILE* fp,png_structp* png_ptr,png_infop* png_info){
	if(fp)
		fclose(fp);

	if(png_ptr)
		png_destroy_read_struct(png_ptr, NULL, NULL);

	if(png_info)
		png_destroy_read_struct(NULL, png_info, NULL);


	throw ICacheException(msg);
}

ImageCache::TexturePtr ImageCache::loadTexture(const std::string& _path,const std::string& name){
	unsigned int width, height;
	png_byte color_type;
	png_byte bit_depth;

	png_structp png_ptr;
	png_infop info_ptr;
	int number_of_passes;
	png_bytep * row_pointers;

	unsigned int mult,y;

	png_byte header[8];	// 8 is the maximum size that can be checked

	/* open file and test for it being a png */
	FILE *fp = fopen((_path+name).c_str(), "rb");
	if (!fp)
		abort("[read_png_file] File " + name + "could not be opened for reading",fp);
	fread(header, 1, 8, fp);
	if (png_sig_cmp(header, 0, 8))
		abort("[read_png_file] File " + name + " not a png",fp);


	/* initialize stuff */
	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);

	if (!png_ptr)
		abort("[read_png_file] png_create_read_struct failed",fp);

	info_ptr = png_create_info_struct(png_ptr);
	if (!info_ptr)
		abort("[read_png_file] png_create_info_struct failed",fp,&png_ptr);

	if (setjmp(png_jmpbuf(png_ptr)))
		abort("[read_png_file] Error during init_io",fp,&png_ptr,&info_ptr);

	png_init_io(png_ptr, fp);
	png_set_sig_bytes(png_ptr, 8);

	png_read_info(png_ptr, info_ptr);

	width = info_ptr->width;
	height = info_ptr->height;
	color_type = info_ptr->color_type;
	bit_depth = info_ptr->bit_depth;

	number_of_passes = png_set_interlace_handling(png_ptr);
	png_read_update_info(png_ptr, info_ptr);

	Texture* _texture = new Texture();

	/* read file */
	if (setjmp(png_jmpbuf(png_ptr)))
		abort("[read_png_file] Error during init_io",fp,&png_ptr,&info_ptr);

	if(color_type==PNG_COLOR_TYPE_RGB_ALPHA){
		mult=4;
		_texture->ctype(Texture::RGBA);
	}else if(color_type==PNG_COLOR_TYPE_RGB){
		mult=3;
		_texture->ctype(Texture::RGB);
	}else if(color_type==PNG_COLOR_TYPE_GRAY){
		mult=1;
		_texture->ctype(Texture::GRAY);
	}else{
		abort("[read_png_file] Not a valid color type",fp,&png_ptr,&info_ptr);
	}

	_texture->data(new unsigned char[width*height*mult]);
	_texture->width(width);
	_texture->height(height);
	_texture->name(name);

	row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * height);
	for (y=0; y<height; y++)
		row_pointers[y] = &_texture->data()[y*width*mult];

	png_read_image(png_ptr, row_pointers);
	png_destroy_read_struct(&png_ptr, &info_ptr, NULL);

	::free(row_pointers);
	fclose(fp);

	return TexturePtr(_texture);
}

void ImageCache::save(const std::string& path,const ImageCache::TexturePtr& texture){
	unsigned int width,height;
	png_byte color_type;
	png_byte bit_depth;

	png_structp png_ptr;
	png_infop info_ptr;
	png_bytep * row_pointers;

	unsigned int mult=3;

	width= texture->width();
	height= texture->height();
	if(texture->ctype()==Texture::RGB){
		color_type = PNG_COLOR_TYPE_RGB;
	}else if(texture->ctype()==Texture::RGBA){
		color_type = PNG_COLOR_TYPE_RGB_ALPHA;
		mult=4;
	}else if(texture->ctype()==Texture::GRAY){
		color_type = PNG_COLOR_TYPE_GRAY;
		mult=1;
	}else{
		abort("[write_png_file] Not a valid color type");
	}
	bit_depth = 8; // No se utiliza 16

	row_pointers = new png_bytep[height];
	for(unsigned int c=0;c<height;c++){
		row_pointers[c] = (png_bytep)(&texture->data()[c*width*mult]);
	}

	/* create file */
	FILE *fp = fopen((path+texture->name()).c_str(), "wb");
	if (!fp)
		abort("[write_png_file] File " + texture->name() + " could not be opened for writing",fp);

	/* initialize stuff */
	png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);

	if (!png_ptr)
		abort("[write_png_file] png_create_write_struct failed",fp,&png_ptr);

	info_ptr = png_create_info_struct(png_ptr);
	if (!info_ptr)
		abort("[write_png_file] png_create_info_struct failed",fp,&png_ptr,&info_ptr);

	if (setjmp(png_jmpbuf(png_ptr)))
		abort("[write_png_file] Error during init_io",fp,&png_ptr,&info_ptr);

	png_init_io(png_ptr, fp);

	/* write header */
	if (setjmp(png_jmpbuf(png_ptr)))
		abort("[write_png_file] Error during writing header",fp,&png_ptr,&info_ptr);

	png_set_IHDR(png_ptr, info_ptr, width, height,
			 bit_depth, color_type, PNG_INTERLACE_NONE,
			 PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);

	png_write_info(png_ptr, info_ptr);

	/* write bytes */
	if (setjmp(png_jmpbuf(png_ptr)))
		abort("[write_png_file] Error during writing bytes",fp,&png_ptr,&info_ptr);

	png_write_image(png_ptr, row_pointers);

	/* end write */
	if (setjmp(png_jmpbuf(png_ptr)))
		abort("[write_png_file] Error during end of write",fp,&png_ptr,&info_ptr);

	png_write_end(png_ptr, NULL);

	delete[] row_pointers;

	fclose(fp);
}


ImageCache::TexturePtr ImageCache::load(const std::string& path,const std::string& name){
	TexturePtr ret;
	if(cache.find(name)==cache.end()){
		ret = loadTexture(path,name);
		cache.insert(std::make_pair(name,ret));
	}else{
		ret = cache.find(name)->second;
	}

	return ret;
}

void ImageCache::free(TexturePtr& ptr){
	if(cache.find(ptr->name())==cache.end())
		return;

	if(cache.find(ptr->name())->second.use_count()==2){
		cache.erase(ptr->name());
	}

	Texture* nullTex = NULL;
	TexturePtr nullPtr(nullTex);
	ptr=nullPtr;
}

void ImageCache::clear(){
	cache.clear();
}

